/*
Write a program that lists the process ID and command name for all processes
being run by the user named in the program’s command-line argument. (You may
find the userIdFromName() function from Listing 8-1, on page 159, useful.) This can
be done by inspecting the Name: and Uid: lines of all of the /proc/ PID /status files on
the system. Walking through all of the /proc/ PID directories on the system requires the
use of readdir(3), which is described in Section 18.8. Make sure your program
correctly handles the possibility that a /proc/ PID directory disappears between the
time that the program determines that the directory exists and the time that it tries
to open the corresponding /proc/ PID /status file.
*/

#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <dirent.h>
#include <sys/types.h>
#include <stdbool.h>
#include <linux/limits.h>
#include <linux/sched.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// BSD declared
#include <err.h>

#include "../common/logger.h"

#define ARGUMENTS_COUNT 2
#define ARGUMENT_USER_NAME 1
#define PROC_DIR "/proc/"

uid_t
get_user_id_from_name(const char pw_name[static 1])
{
  struct passwd *pwd = getpwnam(pw_name);
  if (pwd == NULL)
    return -1;

  return pwd->pw_uid;
}

struct process
{
  char cmd_name[NAME_MAX];
  pid_t process_id;
};

#define MAX_LINE 100
#define UID_STR "Uid:"
#define NAME_STR "Name:"
#define PID_STR "Pid:"
#define LEN_STR(STR) sizeof(STR)/sizeof(char) - 1

int
main(int argc, char *argv[])
{
  if (argc != ARGUMENTS_COUNT)
    log_error("Invalid argument");

  const char *user_name = argv[ARGUMENT_USER_NAME];
  const uid_t id = get_user_id_from_name(user_name);
  if (id == -1)
    log_error("Invalid user name");


  DIR* proc_dir = opendir(PROC_DIR);
  if (proc_dir == NULL)
    log_system_error("Failed to open proc directory");

  struct dirent *dirent;
  char path[PATH_MAX];
  const char *separator = " \t\n";
  while ((dirent = readdir(proc_dir)) != NULL)
    {
      if (dirent->d_type == DT_DIR)
        {
          if (atoi(dirent->d_name) == 0) continue;
          sprintf(path, "%s/%s/status", PROC_DIR, dirent->d_name);
          FILE *proc_status_fd = fopen(path, "r");
          if (proc_status_fd == NULL)
            {
              log_system_warning("failed to open file");
              continue;
            }
          char *line = NULL;
          size_t length;
          struct process process;
          bool is_user_process = false;
          while (getline(&line, &length, proc_status_fd) > 0)
            {
              if (strncmp(line, NAME_STR, LEN_STR(NAME_STR)) == 0)
                {
                  char *argument = strpbrk(line, separator);
                  if (argument) argument += strspn(argument, separator);
                  else log_error("Failed to read an argument");
                  char *argument_end = strpbrk(argument, separator);
                  snprintf(process.cmd_name, argument_end - argument + 1, "%s",
                           argument);
                }

              if (strncmp(line, UID_STR, LEN_STR(UID_STR)) == 0)
                {
                  char *argument = strpbrk(line, separator);
                  if (argument) argument += strspn(argument, separator);
                  else log_error("Failed to read an argument");
                  long long num = strtoll(argument, NULL, 10);
                  if (num == id) is_user_process = true;
                }

              if (strncmp(line, PID_STR, LEN_STR(PID_STR)) == 0)
                {
                  char *argument = strpbrk(line, separator);
                  if (argument) argument += strspn(argument, separator);
                  else log_error("Failed to read an argument");
                  long long num = strtoll(argument, NULL, 10);
                  process.process_id = num;
                }
            }
          free(line);
          fclose(proc_status_fd);

          if (is_user_process)
            {
              printf("process name: %s\n", process.cmd_name);
              printf("process id: %ld\n", process.process_id);
              puts("=================\n");
            }
        }
    }
  closedir(proc_dir);
  return EXIT_SUCCESS;
}
